home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Language/OS - Multiplatform Resource Library
/
LANGUAGE OS.iso
/
lisp
/
clx.lha
/
clx
/
regions.l
< prev
next >
Wrap
Lisp/Scheme
|
1988-09-12
|
25KB
|
673 lines
;;; -*- Mode:Lisp; Package:XLIB; Syntax:COMMON-LISP; Base:10; Lowercase:T -*-
;;; Regions
;;;
;;; TEXAS INSTRUMENTS INCORPORATED
;;; P.O. BOX 2909
;;; AUSTIN, TEXAS 78769
;;;
;;; Copyright (C) 1987 Texas Instruments Incorporated.
;;;
;;; Permission is granted to any individual or institution to use, copy, modify,
;;; and distribute this software, provided that this complete copyright and
;;; permission notice is maintained, intact, in all copies and supporting
;;; documentation.
;;;
;;; Texas Instruments Incorporated provides this software "as is" without
;;; express or implied warranty.
;;;
(in-package 'xlib :use '(lisp))
(export '( region
make-region
copy-region
region-clip-box
region-x
region-y
region-width
region-height
region-x ;; SETF'able
region-y ;; SETF'able
region-empty-p
region-intersection
region-union
region-subtract
point-in-region-p
region-equal
subregion-p
region-intersect-p
map-region
region->image
IMAGE->REGION ;; not implemented
POLYGON-REGION ;; not implemented
))
;;; Regions are arbitrary collections of pixels. This is represented
;;; in the region structure as either a list of rectangles or a bitmap.
(defstruct (region (:constructor create-region) (:conc-name region-)
(:predicate region-p)
(:copier copy-region-structure)
#+ti (:callable-constructors nil))
(left 0 :type integer)
(top 0 :type integer)
(right 0 :type integer)
(bottom 0 :type integer)
(next nil)
)
;;; NOTE: This is inefficient. Should be:
#|
(defstruct (rectangle #+ti (:callable-constructors nil))
(left 0 :type integer)
(top 0 :type integer)
(right 0 :type integer)
(bottom 0 :type integer)
)
(defstruct (region (:include rectangle) ;; Bounding rectangle
(:constructor create-region)
(:copier copy-region-structure)
#+ti (:callable-constructors nil))
(elements nil) ;; A list of rectangles or a bitmap
(plist nil :type list)
)
|#
(defun make-region (&optional x y width height)
;; With no parameters, returns an empty region
;; If some parameters are given, all must be given.
(declare (type (or null int16) x y width height)
(values region))
(if x
(create-region :left x :top y :right (+ x width) :bottom (+ y height))
(create-region)))
(defun copy-region (region)
(let ((new (copy-region-structure region))
(next (region-next region)))
(when next
(setf (region-next new)
(copy-region next)))
new))
;; Accessors that return the boundaries of a region
(defun region-x (region)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((reg (region-next region) (region-next reg))
(top (region-top region)))
((null reg) top)
(setq top (min top (region-top reg)))))
(defun region-y (region)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((reg (region-next region) (region-next reg))
(left (region-left region)))
((null reg) left)
(setq left (min left (region-left reg)))))
(defun region-width (region)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((reg (region-next region) (region-next reg))
(left (region-left region))
(right (region-right region)))
((null reg) (- right left))
(setq left (min left (region-left reg))
right (max right (region-right reg)))))
(defun region-height (region)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((reg (region-next region) (region-next reg))
(top (region-top region))
(bottom (region-bottom region)))
((null reg) (- bottom top))
(setq top (min top (region-top reg))
bottom (max bottom (region-bottom reg)))))
(defsetf region-x set-region-x)
(defsetf region-y set-region-y)
;; Setting a region's X/Y translates the region
(defun set-region-x (region value)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((delta (- value (region-x region)))
(reg region (region-next region)))
((null reg) value)
(incf (region-left reg) delta)
(incf (region-right reg) delta)))
(defun set-region-y (region value)
(declare (type region region)
(values integer))
;; Loop over regions
(do ((delta (- value (region-y region)))
(reg region (region-next region)))
((null reg) value)
(incf (region-top reg) delta)
(incf (region-bottom reg) delta)))
(defun region-empty-p (region)
(declare (type region region)
(values boolean))
;; Loop over regions
(do ((reg region (region-next reg)))
((null reg) t)
(declare (type (or null region) reg))
(unless (and (= (region-left reg) (region-right reg))
(= (region-top reg) (region-bottom reg)))
(return nil))))
(defun region-clip-box (&rest regions)
"Returns a region which is the smallest enclosing rectangle enclosing REGIONS
Returns the empty region if no regions are given."
(declare (type list regions) ;; (list region)
(values region))
(let ((result (create-region))
(first-region (car regions)))
(DECLARE (TYPE region result)
(TYPE (OR null region) first-region))
(when first-region
(let ((left (region-left first-region))
(right (region-right first-region))
(top (region-top first-region))
(bottom (region-bottom first-region)))
(DECLARE (TYPE integer left right top bottom))
(dolist (region regions)
(declare (type region region))
(do ((reg region (region-next region)))
((null reg))
(declare (type (or null region) reg))
(unless (eq reg first-region)
(setq left (min left (region-left region))
top (min top (region-top region))
right (max right (region-right region))
bottom (max bottom (region-bottom region))))))
(setf (region-left result) left
(region-right result) right
(region-top result) top
(region-bottom result) bottom)))
result))
(defun region-intersection (&rest regions)
"Returns a region which is the intersection of one or more REGIONS.
Returns an empty region if the intersection is empty.
If there are no regions given, return a very large region."
(declare (type list regions) ;; (list region)
(values region))
(IF regions
(LET ((result nil))
(do ((region regions (CDR region)))
((NULL (CDR region))
(OR result (create-region)))
(do ((region1 (FIRST region) (region-next region1)))
((null region1))
(DOLIST (reg2 (CDR region))
(DO ((region2 reg2 (region-next region2)))
((NULL region2))
(WHEN (NOT (OR (<= (region-right region2) (region-left region1))
(<= (region-right region1) (region-left region2))
(<= (region-bottom region2) (region-top region1))
(<= (region-bottom region1) (region-top region2))))
(SETQ result
(region-nconc-rectangle
result
(MAX (region-left region1) (region-left region2))
(MAX (region-top region1) (region-top region2))
(min (region-right region1) (region-right region2))
(min (region-bottom region1) (region-bottom region2))))))))))
(create-region :left most-negative-fixnum :top most-negative-fixnum
:right most-positive-fixnum :bottom :most-positive-fixnum)))
(defun region-subtract (region subtract)
"Returns a region containing the points that are in REGION but not in SUBTRACT"
(declare (type region region subtract)
(values region))
(do ((region-to-subtract subtract (region-next region-to-subtract))
(result nil))
((null region-to-subtract) (or result (create-region)))
(do ((stop (region-top region-to-subtract))
(sleft (region-left region-to-subtract))
(sbottom (region-bottom region-to-subtract))
(sright (region-right region-to-subtract))
(region region (region-next region)))
((null region))
(let ((itop (region-top region))
(ileft (region-left region))
(ibottom (region-bottom region))
(iright (region-right region))
(intersect nil))
(when (not (or (<= iright sleft)
(<= sright ileft)))
(when (< itop stop ibottom) ;TOP
;; The regions in this case look like the following.
;; .-------------------.
;; | intersect |
;; .--+-------------------+--.
;; | | | |
;; | | | |
;; | | | |
;; | `-------------------' |
;; | |
;; | |
;; | subtract |
;; `-------------------------'
;;
(setq intersect t)
(setq result (region-nconc-rectangle result ileft itop iright stop)))
(when (< itop sbottom ibottom) ;BOTTOM
;; The regions in this case look like the following.
;; .-------------------------.
;; | subtract |
;; | |
;; | |
;; | .___________________. |
;; | | | |
;; | | | |
;; | | | |
;; `--+-------------------+--'
;; | intersect |
;; `-------------------'
;;
(setq intersect t)
(setq result (region-nconc-rectangle result ileft sbottom iright ibottom))))
(when (not (or (<= ibottom stop)
(<= sbottom itop)))
(when (< ileft sleft iright) ;LEFT
;; The regions in this case look like the following.
;; .-----------------.
;; | |
;; .-------------+----. |
;; | | | |
;; | | | |
;; | intersect | | subtract |
;; | | | |
;; | | | |
;; `-------------+----' |
;; | |
;; `-----------------'
;;
(setq intersect t)
(setq result (region-nconc-rectangle
result ileft (max itop stop) sleft (min ibottom sbottom))))
(when (< ileft sright iright) ;RIGHT
;; The regions in this case look like the following.
;; .-----------------.
;; | |
;; | .----+-------------.
;; | | | |
;; | | | |
;; | subtract | | intersect |
;; | | | |
;; | | | |
;; | `----+-------------'
;; | |
;; `-----------------'
;;
(setq intersect t)
(setq result (region-nconc-rectangle
result sright (max itop stop) iright (min ibottom sbottom)))))
(unless (or intersect ;Save regions not split above
(and ;Don't save regions covered by region-to-subtract
(>= ileft sleft)
(>= itop stop)
(<= iright sright)
(<= ibottom sbottom)))
;; The regions in this case do NOT look like the following.
;; .----------------------.
;; | subtract |
;; | |
;; | .-------------. |
;; | | | |
;; | | | |
;; | | intersect | |
;; | | | |
;; | | | |
;; | `-------------' |
;; | |
;; `----------------------'
;;
(SETQ result (region-nconc-rectangle result ileft itop iright ibottom)))))))
;; Internal function used by region-subtract and region-intersection
(defun region-nconc-rectangle (region left top right bottom)
;; Destructively modify REGION to include a rectangle
(DECLARE (type region region)
(type integer left top right bottom))
(do* ((reg1 (create-region :left left :top top :right right :bottom bottom :next region))
(previous reg1 reg2)
(reg2 (region-next reg1) (region-next reg2)))
((null reg2) reg1)
(COND ((and (= left (region-left reg2))
(= right (region-right reg2)))
;; left/right Edges line up.
;; Check for region adjacent to top or bottom
(when
(cond ((= top (region-bottom reg2))
(SETQ top (setf (region-top reg1) (region-top reg2))))
((= bottom (region-top reg2))
(SETQ bottom (setf (region-bottom reg1) (region-bottom reg2)))))
;; Reg1 has been modified to include reg2. Splice out reg2
(setf (region-next previous) (region-next reg2))))
((and (= top (region-top reg2))
(= bottom (region-bottom reg2)))
;; top/bottom Edges line up.
;; Check for region adjacent to left or right.
(when
(cond ((= left (region-right reg2))
(SETQ left (setf (region-left reg1) (region-left reg2))))
((= right (region-left reg2))
(SETQ right (setf (region-right reg1) (region-right reg2)))))
;; Reg1 has been modified to include reg2. Splice out reg2
(setf (region-next previous) (region-next reg2)))))))
(DEFUN union-regions (&rest regions)
;; Append REGIONS, avoiding region fragmentation.
(APPLY 'nunion-regions (MAPCAR #'copy-region regions)))
(defun nunion-regions (&rest region-list)
;; Append REGIONS, avoiding region fragmentation.
(DO* ((result (OR (CAR region-list) (create-region)))
(regions (CDR region-list) (CDR regions))
(nintersections 0)) ;; Number of intersections
((ENDP regions)
(IF (> nintersections 1)
(unfragment-region result)
result))
(DECLARE (type region result)
(type list regions))
;; Merge region with result
(DO* ((region (CAR regions))
(last nil reg1) ; Loop over regions in result
(reg1 result (region-next reg1)))
((NULL reg1) ;; Link new regions into result
(SETF (region-next last) region))
(DECLARE (TYPE region region)
(TYPE (OR null region) last reg1))
(do* ((previous nil reg2) ; Loop over new regions
(reg2 region (region-next reg2))
(left1 (region-left reg1))
(right1 (region-right reg1))
(top1 (region-top reg1))
(bottom1 (region-bottom reg1))
(left2) (right2)(top2)(bottom2))
((null reg2))
(DECLARE (TYPE (OR null region) previous reg2))
(WHEN (and (> (SETQ right2 (region-right reg2)) left1)
(> right1 (SETQ left2 (region-left reg2)))
(> (SETQ bottom2 (region-bottom reg2)) top1)
(> bottom1 (SETQ top2 (region-top reg2))))
(INCF nintersections)
;; regions intersect, merge them
;;
;; .----------------------. .----------------------.
;; | A | | : : |
;; |~~~~-------------~~~~~| | .-------------. |
;; | | | | | | | |
;; | | | | | | | |
;; | | Reg1 | | | C | Reg1 | D |
;; | | | | | | | |
;; | | | | | | | |
;; |~~~`-------------'~~~~| | `-------------' |
;; | B | | : : |
;; `----------------------' `----------------------'
;;
(WHEN (> top1 top2) ; Case A
(SETQ region ;; Create extra region for portion of reg2 above reg1
(create-region
:next region
:left left2 :top top2 :right right2 :bottom top1))
(UNLESS previous (SETQ previous region))
(SETQ top2 (SETF (region-top reg2) top1)))
(WHEN (> bottom2 bottom1) ; Case B
(SETQ region ;; Create extra region for portion of reg2 below reg1
(create-region
:next region
:left left2 :top bottom1 :right right2 :bottom bottom2))
(UNLESS previous (SETQ previous region))
(SETQ bottom2 (SETF (region-bottom reg2) bottom1)))
(WHEN (> left1 left2) ; Case C
(SETQ region ;; Create extra region for portion of reg2 to the left of reg1
(create-region
:next region
:left left2 :top top2 :right left1 :bottom bottom2))
(UNLESS previous (SETQ previous region))
(SETQ left2 (SETF (region-left reg2) left1)))
(WHEN (> right2 right1) ; Case D
(SETQ region ;; Create extra region for portion of reg2 to the right of reg1
(create-region
:next region
:left right1 :top top2 :right right2 :bottom bottom2))
(UNLESS previous (SETQ previous region))
(SETQ right2 (SETF (region-right reg2) right1)))
;; Check for reg2 INSIDE reg1
(when (and (>= left2 left1)
(>= top2 top1)
(<= right2 right1)
(<= bottom2 bottom1))
;; Splice out reg2
(IF previous
(setf (region-next previous) (region-next reg2))
(SETQ region (region-next reg2)))))))))
(DEFUN unfragment-region (region)
;; Regions sometimes get fragmented. This function looks for
;; two sub-regions within region that can be combined and
;; destructively modifies the region to do the combination.
(loop
(unless ;; Keep looping until no fragments found
(block success
(do ((reg1 region (region-next reg1)))
((null reg1))
(do* ((left (region-left reg1))
(right (region-right reg1))
(top (region-top reg1))
(bottom (region-bottom reg1))
(previous reg1 reg2)
(reg2 (region-next reg1) (region-next reg2)))
((null reg2))
(when (and (= left (region-left reg2))
(= right (region-right reg2)))
;; left/right Edges line up.
;; Check for region adjacent to top or bottom
(when
(cond ((= top (region-bottom reg2))
(setf (region-top reg1) (region-top reg2)))
((= bottom (region-top reg2))
(setf (region-bottom reg1) (region-bottom reg2))))
;; Reg1 has been modified to include reg2. Splice out reg2
(setf (region-next previous) (region-next reg2))
(return-from success t)))
(when (and (= top (region-top reg2))
(= bottom (region-bottom reg2)))
;; top/bottom Edges line up.
;; Check for region adjacent to left or right.
(when
(cond ((= left (region-right reg2))
(setf (region-left reg1) (region-left reg2)))
((= right (region-left reg2))
(setf (region-right reg1) (region-right reg2))))
;; Reg1 has been modified to include reg2. Splice out reg2
(setf (region-next previous) (region-next reg2))
(return-from success t))))))
(return region))))
(defun point-in-region-p (region x y)
;; Returns T when X/Y are a point within REGION.
(declare (type region region)
(type int16 x y)
(values boolean))
;; Loop over regions
(do ((reg region (region-next reg)))
((NULL reg) nil)
(when (and (>= x (region-left reg))
(>= y (region-top reg))
(< x (region-right reg))
(< y (region-bottom reg)))
(return t))))
;; NOTE: NOT ROBUST WHEN A and B are fragmented differently *************
(defun region-equal (a b)
;; Returns T when regions a and b contain the same points.
;; That is, return t when for every X/Y (point-in-region-p a x y)
;; equals (point-in-region-p b x y)
(declare (type region a b)
(values boolean))
(do ((region1 a (region-next region1)))
((null region1) t)
(let ((left (region-left region1))
(right (region-right region1))
(top (region-top region1))
(bottom (region-bottom region1)))
(do ((region2 b (region-next region2)))
((null region2) (return-from region-equal nil))
(when (and (= left (region-left region2))
(= right (region-right region2))
(= top (region-top region2))
(= bottom (region-bottom region2)))
(return t))))))
;; NOTE: NOT ROBUST WHEN LARGE and SMALL are fragmented differently *************
(defun subregion-p (large small)
"Returns T if SMALL is within LARGE.
That is, return T when for every X/Y (point-in-region-p small X Y)
implies (point-in-region-p large X Y)."
(declare (type region large small)
(values boolean))
(do ((large-region large (region-next large-region)))
((null large-region) nil)
(do ((small-region small (region-next small-region)))
((null small-region) nil)
(when (and (>= (region-left small-region) (region-left large-region))
(>= (region-top small-region) (region-top large-region))
(<= (region-right small-region) (region-right large-region))
(<= (region-bottom small-region) (region-bottom large-region)))
(return-from subregion-p t)))))
(defun region-intersect-p (a b)
"Returns T if A intersects B.
That is, return T when there is some point common to regions A and B."
(declare (type region a b)
(values boolean))
(do ((region1 a (region-next region1)))
((null region1) nil)
(let ((left (region-left region1))
(right (region-right region1))
(top (region-top region1))
(bottom (region-bottom region1)))
(do ((region2 b (region-next region2)))
((null region2))
(when (and (> (region-right region2) left)
(> right (region-left region2))
(> (region-bottom region2) top)
(> bottom (region-top region2)))
(return-from region-intersect-p t))))))
(defun map-region (region function &rest args)
;; Calls function with arguments (x y . args) for every point in REGION.
(declare (type region region)
(type function function))
(do ((reg region (region-next reg)))
((null reg))
(do* ((left (region-left reg))
(right (region-right reg))
(top (region-top reg))
(bottom (region-bottom reg))
(x left (1+ x)))
((>= x right))
(do ((y top (1+ y)))
((>= y bottom))
(apply function x y args)))))
(defun map-region-rectangles (region function &rest args)
;; Calls function with arguments (x y width height . args) for every rectangle in REGION.
(declare (type region region)
(type function function))
(do ((reg region (region-next reg)))
((null reg))
(let ((left (region-left reg))
(right (region-right reg))
(top (region-top reg))
(bottom (region-bottom reg)))
(apply function left top (- right left) (- bottom top) args))))
;; Why isn't it better to augment
;; gcontext-clip-mask to deal with
;; (or null (member :none) pixmap rect-seq region)
;; and force conversions on the caller?
;; Good idea.
;;(defun gcontext-clip-region (gcontext)
;; ;; If the clip-mask of GCONTEXT is known, return it as a region.
;; (declare (type gcontext gcontext)
;; (values (or null region))))
;;(defsetf gcontext-clip-region (gcontext) (region)
;; ;; Set the clip-rectangles or clip-mask for for GCONTEXT to include
;; ;; only the pixels within REGION.
;; (declare (type gcontext gcontext)
;; (type region region)))
(DEFUN region-length (region &optional (limit 100))
;; Debug function ***********
(DO ((reg region (region-next reg))
(n 0 (1+ n)))
((NULL reg) n)
(WHEN (> n limit)
(ERROR "Region length longer than ~d" limit))))
#+comment
(defun image->region (image)
;; Returns a region containing the 1 bits of a depth-1 image
;; Signals an error if image isn't of depth 1.
(declare (type image image)
(values region)))
(defun region->image (region)
;; Returns a depth-1 image containg 1 bits for every pixel in REGION.
(declare (type region region)
(values image))
(let* ((box (region-clip-box region))
(width (region-width box))
(height (region-height box))
(next-p (region-next region))
(pixarray (make-array (list width height) :element-type 'bit
:initial-contents (if next-p 0 1))))
(when next-p ;; Optimize for the rectangular case
(setf (region-x box) 0
(region-y box) 0)
(do ((reg region (region-next reg)))
((null reg))
;; There's gotta be a faster way...
(do* ((left (region-left reg))
(right (region-right reg))
(top (region-top reg))
(bottom (region-bottom reg))
(x left (1+ x)))
((>= x right))
(do ((y top (1+ y)))
((>= y bottom))
(setf (aref pixarray y x) 1)))))
(create-image :width width
:height height
:depth 1
:data pixarray)))
#+comment
(defun polygon-region (points &optional (fill-rule :even-odd))
(declare (type sequence points) ;(repeat-seq (integer x) (integer y))
(type (member :even-odd :winding) fill-rule)
(values region)))